#pragma once
#include <iostream>
#include <sys/select.h>
#include "Socket.hpp"
#include <bits/time.h>
#include <string>

using namespace socket_ns;

class SelectServer
{
    const static int N = sizeof(fd_set) * 8;
    const static int defaultfd = -1;

public:
    SelectServer(uint16_t port)
        : _port(port), _listenSock(std::make_unique<TcpSocket>())
    {
        InetAddr addr("0", _port);
        _listenSock->BuildListenSocket(addr);
        for (int i = 0; i < N; i++)
        {
            _fd_array[i] = defaultfd;
        }
        // 刚开始只有一个rfd，就是listen套接字
        _fd_array[0] = _listenSock->SockFd();
    }
    void AcceptClint()
    {
        InetAddr client_addr;
        int sockfd = _listenSock->Accepter(&client_addr); // 这里修改一下Accepter函数，返回值用原始的文件描述符
        if (sockfd < 0)
        {
            return;
        }
        LOG(INFO, "get new link success sockfd:%d client info %s:%d\n", sockfd, client_addr.Ip().c_str(), client_addr.Port());
        int pos = 1;
        // 找到新增文件描述符插入的位置
        for (; pos < N; pos++)
        {
            if (_fd_array[pos] == defaultfd)
                break;
        }
        if (pos == N)
        {
            ::close(sockfd);
            LOG(DEBUG, "_fd_array full!\n");
            return;
        }
        else
        {
            _fd_array[pos] = sockfd;
            LOG(DEBUG, "%d add to _fd_array\n", sockfd);
        }
        LOG(INFO, "_fd_array list is %s\n", RfdsToStr().c_str()); // 查看一下此时新增一共有多少个有效需要关心的文件描述符
    }
    std::string RfdsToStr()
    {
        std::string rfdstr;
        for (int i = 0; i < N; i++)
        {
            if (_fd_array[i] == defaultfd)
                continue;
            rfdstr += std::to_string(_fd_array[i]);
            rfdstr += " ";
        }
        return rfdstr;
    }
    void ServiceIo(int pos)
    {
        char buffer[1024];
        ssize_t n = ::recv(_fd_array[pos], buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client say#" << buffer << std::endl;
            std::string resp = "[Server Echo]# ";
            resp += buffer;
            ::send(_fd_array[pos], resp.c_str(), resp.size(), 0);
        }
        else if (n == 0)
        {
            LOG(DEBUG, "%d is closed\n", _fd_array[pos]);
            ::close(_fd_array[pos]);
            _fd_array[pos] = defaultfd;
            LOG(INFO, "_fd_array list is %s\n", RfdsToStr().c_str()); // 查看一下此时减少一个后一共有多少个有效需要关心的文件描述符
        }
        else
        {
            LOG(DEBUG, "%d recv error\n", _fd_array[pos]);
            ::close(_fd_array[pos]);
            _fd_array[pos] = defaultfd;                               // 关闭文件描述符，并将其从我们的辅助数组里面移除掉，这样下次select不会再关心这个无效的文件描述符
            LOG(INFO, "_fd_array list is %s\n", RfdsToStr().c_str()); // 查看一下此时减少一个后一共有多少个有效需要关心的文件描述符
        }
    }
    void Handler(fd_set &rfds)
    {
        // 处理的时候可能是一个或者多个就绪，我们需要一个一个处理
        for (int i = 0; i < N; i++)
        {
            if (_fd_array[i] == defaultfd)
                continue;
            if (FD_ISSET(_fd_array[i], &rfds))
            {
                // 合法的话，判断是否是listen套接字就绪新增文件描述符，还是normal套接字就绪正常处理消息接收和发送
                if (_fd_array[i] == _listenSock->SockFd())
                {
                    AcceptClint();
                }
                else
                {
                    ServiceIo(i);
                }
            }
        }
    }
    void Loop()
    {
        while (true)
        {
            // 由于select输入输出是一体的，所以每次需要根据辅助数组，重新设定我们需要关心的rfd
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_rfd = defaultfd;
            for (int i = 0; i < N; i++)
            {
                if (_fd_array[i] == defaultfd)
                    continue;
                FD_SET(_fd_array[i], &rfds);
                if (max_rfd < _fd_array[i])
                    max_rfd = _fd_array[i];
            }
            // 开始利用select进行等待
            struct timeval timeout = {0, 0};
            int n = ::select(max_rfd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
            switch (n)
            {
            case 0:
                LOG(INFO, "timeout, %d.%d\n", timeout.tv_sec, timeout.tv_usec); // 刚开始只有一个listen套接字，没人链接会一直超时
                break;
            case -1:
                LOG(ERROR, "select error\n");
                break;
            default:
            {
                LOG(DEBUG, "Events happen.. n: %d\n", n); // 虽然刚开始只有一个listen套接字，一旦有人连接，就会一直就绪，我们需要处理事件
                Handler(rfds);
            }
            break;
            }
        }
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listenSock;
    int _fd_array[N]; // 辅助数组，将来要关心的rfd都放到辅助数组里面
};
